package Algorithms;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Random;

import Stack.Stack;
import Stack.StackIterator;
import Templates.Operator;
import Templates.State;

public class SimulatedAnnealingSearch<Template> {
	private Stack<State<Template>> btStack ;
	private ArrayList<Operator<Template>> operators;
	private State<Template> initialState;
	private ArrayList<State<Template>> finalStates;
	private int operatorNumber =0;
	private PrintWriter logWriter = null;
	private File log;
	private int expandCounter=0;
	private double T;
	private double []schedule;
	private int successorsNo;
	protected double temp; 
	protected double coolingRate; 
	protected double tolerance;

	/** default temperature */
	static public final double DEFAULTTEMP=1.e+6;
	static public final double DEFAULTCOOLINGRATE=20;
	static public final double DEFAULTOLERANCE=1.e-5;

	/**
	 * 
	 * @param iState Initial State
	 * @param fStates Final States
	 * @param opr Operators
	 * @param log Log File
	 */
	public SimulatedAnnealingSearch(State<Template> iState,ArrayList<State<Template>> fStates,ArrayList<Operator<Template>> opr,String log)
	{
		this.temp = DEFAULTTEMP;
		this.coolingRate = DEFAULTCOOLINGRATE;
		this.tolerance = DEFAULTOLERANCE;
		btStack=new Stack<State<Template>>(100);
		initialState=iState;
		finalStates=fStates;
		if(opr != null){
			operators=opr;
			operatorNumber=operators.size()-1;
		}
		try {
			logWriter = new PrintWriter(new FileWriter(log));
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.log=new File(log);
	}
	
	/**
	 * 
	 * @param thisState Current State
	 * @return True if the State is found, False otherwise
	 */
	private boolean found(State<Template> thisState)
	{
		for(StackIterator<State<Template>> it=new StackIterator<State<Template>>(btStack); it.hasNext();)
		{
			if(thisState.equals(it.next())) return true;
		}
		return false;
	}
	
	/**
	 * 
	 * @param thisState Current State
	 * @return True if Current State is one of the Final States
	 */
	private boolean solutionFound(State<Template> thisState)
	{
		for(int i=0; i<finalStates.size();i++)
			if (thisState.equals(finalStates.get(i)))
				return true;
		return false;
	}
	
	/**
	 * 
	 * @param operator
	 * @return
	 */
	private int getIndex(Operator<Template> operator)
	{
		for(int i=0;i<operators.size();i++)
		{
			if (operators.get(i).getOperatorNumber()==operator.getOperatorNumber()) 
				return i;
		}
		return 0;
	}
	
	/**
	 * 
	 * @param thisState Current State
	 * @return True if One of the Successors is Solution
	 * False otherwise
	 */
	private boolean expand(State<Template> thisState)
	{
		for(int i=0;i<=operatorNumber;i++)
		{
			if (operators.get(i).available(thisState.getCurrentState())) 
				{
					State<Template> auxiliary=new State<Template>(operators.get(i).execute(thisState.getCurrentState()), operators.get(i));
					if (!found(auxiliary)) { 
						expandCounter++; btStack.push(auxiliary); 
						if (solutionFound(auxiliary)) return true; 
					}
				}
		}
		return false;
	}
	
	/**
	 * 
	 * @param thisState Current State 
	 * @return True if there is a Solution, False otherwise
	 */
	private boolean solve(State<Template> thisState)
	{
		State<Template> state = intermediar(thisState);
		btStack.push(state);
		solve(btStack.peek());
		return true;
	}
	
	/**
	 * 
	 * @param thisState Current State
	 * @return a Solution State
	 */
	public State<Template> intermediar(State<Template> thisState)
	{
		State<Template> next;
		int dif;
		for(int t = 1;;t++){
			temp *= (100. - coolingRate) / 100.;
			T = temp;
			if(T == 0)
				return thisState;
			if(expand(thisState))
				return null;
			else
			{
				int nr = expandCounter;
				Random r = new Random();
				int nr1 = r.nextInt(nr);
				next = btStack.get(btStack.size()-nr+nr1);
				dif = thisState.getH()-next.getH();
				if(dif>0)
					return next;
				else{
					double v = Math.exp((double)dif/T);
					if(v>1)
						return next;
				}
			}
		}
	}

	/**
	 * 
	 * @param thisState Current State
	 */
	private void print(State<Template> thisState)
	{
		logWriter.append("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
		logWriter.append(thisState.getCurrentOperator().toString());
		logWriter.append('\n');
		logWriter.append(thisState.getCurrentState().toString());
		logWriter.append('\n');

	}
	
	private void closeLog()
	{
		System.out.println("Solution found using Indepth Search... Stacktrace can be found in: " + log.getAbsolutePath());
		logWriter.close();
	}
	
	public void executeAlgorithm()
	{
		btStack.push(initialState);
		solve(initialState);
		for(StackIterator<State<Template>> it=new StackIterator<State<Template>>(btStack); it.hasNext();)
		{
			print(it.next());
		}
		closeLog();
	}

}
